package com.mt.common.core.utils;

import cn.hutool.db.DbUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Map;
import java.util.Random;

/**
 * @ClassName: PrimaryGenerator
 * @Description:
 * @Author zhanghong
 * @Date 2022/5/13
 */
public class PrimaryGenerator {
    private static final Logger logger = LoggerFactory.getLogger(PrimaryGenerator.class);

    // 单例模式
    private static PrimaryGenerator primaryGenerator = new PrimaryGenerator();

    private PrimaryGenerator() {
    }

    /**
     * 对外提供单例对象
     *
     * @return
     */
    public static PrimaryGenerator getInstance() {
        return primaryGenerator;
    }

    /**
     * @title 生成一个唯一编号
     * @desc
     * @author W.jw
     * @date 2018/5/9 14:23
     */
    public synchronized String make() {

        // 获取当前系统的时间戳
        Date now = new Date();
        long timestamp = now.getTime();

        /**
         * 判断当前时间戳和上一次产生流水号的时间是否相等
         *   如果不相等，可以使用当前时间戳
         *   如果相等，判断并发数是否达到最大值
         *     如果达到最大值，需要等待下一个毫秒作为新的时间戳
         *     如果没有达到最大值，则可以使用当前序列值
         */
        if (this.lastTime == timestamp) {
            serial = serial + 1;
            if (serial == maxSerial) {
                timestamp = this.tilNextMillis(this.lastTime);
            }
        } else {
            serial = 0;
        }

        // 将当前系统时间赋值给lastTime，方便下一次做判断
        this.lastTime = timestamp;

        // 保证序列号是3位，产生的订单号长度一样。也可以不补位，订单号长度会短点
        String.format("%03d", serial);

        // 把当前系统时间格式化为yyMMddHHmmssSSS
        String time = DateUtils.dateTimeNow();

        // 唯一流水号 = yyMMddHHmmssSSS+当前服务器唯一编号+序列号
        StringBuilder sb = new StringBuilder(time).append(getMachineId()).append(serial);
        return sb.toString();
    }

    /**
     * 等待下一个毫秒
     *
     * @param currentTime 当前系统时间
     * @return
     */
    private long tilNextMillis(long currentTime) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= currentTime) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 获取当前服务器的编号，固定长度为2位
     * 从数据库中获取当前服务器的ip在服务器中对应的唯一id
     * 如果id存在，则使用id作为唯一编号
     * 如果id不存在，获取当前服务器的ip并插入数据库得到id
     *
     * @return
     */
    private String getMachineId() {

        if (this.machineId != null) {
            return this.machineId;
        }

        /**
         * 获取当前服务器的ip，如果没有获取到ip则随机返回一个两位数
         */
        //String ip = getIp();
        //if (ip == null) {
        // 随机返回两位数并且加上100，保证随机返回的数值和数据库中的id值不冲突
        this.machineId = String.format("%02d", new Random().nextInt(100) + 100);
        //}

        /**
         * 从数据库中获取当前服务器的ip对应的id值
         *   如果没有则插入一条记录并获取id值
         *   如果有则直接使用
         */
        //Map<String, Object> nodeMap = DbUtil.getByIp(ip);
        //if (nodeMap == null || nodeMap.size() == 0) {
        //    try {
        //        DbUtil.save(ip);
        //        nodeMap = DbUtil.getByIp(ip);
        //    } catch (Exception e) {
        //        logger.error("插入集群节点信息失败:{}" , e);
        //        return String.format("%02d", new Random().nextInt(100) + 100);
        //    }
        //}
        //this.machineId = String.format("%02d", nodeMap.get("id"));
        return this.machineId;
    }

    /**
     * 获取当前服务器ip
     *
     * @return
     */
    private static String getIp() {
        InetAddress address = null;
        try {
            address = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
            return null;
        }
        return address.getHostAddress();
    }

    /**
     * 最后一次生成订单号的时间，格式yyMMddHHmmssSSS
     */
    private long lastTime = 0;
    /**
     * 当前服务器在集群内的编号
     */
    private String machineId;
    /**
     * 并发序列号
     */
    private long serial = 0L;
    /**
     * 允许并发的最大值
     */
    private final int maxSerial = 999;

    public static void main(String[] args) {
        System.out.println(PrimaryGenerator.getInstance().make());
    }
}
